package com.chenju.javastatic.service.impl;

import com.chenju.javastatic.common.enums.EasyCode;
import com.chenju.javastatic.common.exceptions.EasyValidationException;
import com.chenju.javastatic.domain.entity.DiffMethodsEntity;
import com.chenju.javastatic.domain.entity.ParsedRecordEntity;
import com.chenju.javastatic.domain.pojo.*;
import com.chenju.javastatic.mybatis.mapper.DiffMethodsMapper;
import com.chenju.javastatic.mybatis.mapper.ParsedRecordsMapper;
import com.chenju.javastatic.service.JavaParserService;
import com.chenju.javastatic.utils.EasyListUtils;
import com.github.javaparser.JavaParser;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.nodeTypes.NodeWithName;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.resolution.SymbolResolver;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.*;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
public class JavaParserServiceImplements implements JavaParserService {
    private final DiffMethodsMapper diffMethodsMapper;
    private final ParsedRecordsMapper parsedRecordsMapper;
    public static List<DiffPoJo> diffPoJoList;

    public JavaParserServiceImplements(DiffMethodsMapper diffMethodsMapper, ParsedRecordsMapper parsedRecordsMapper) {
        this.diffMethodsMapper = diffMethodsMapper;
        this.parsedRecordsMapper = parsedRecordsMapper;
    }

    @Override
    public List<DiffPoJo> parseBranchDiff(String projectName, File repoDir, String oldBranchName, String newBranchName) {
        diffPoJoList = new ArrayList<>();
        try (Repository repository = new RepositoryBuilder().setGitDir(new File(repoDir, ".git")).build()) {
            ObjectId oldObjectId = repository.resolve("refs/heads/" + oldBranchName);
            ObjectId newObjectId = repository.resolve("refs/heads/" + newBranchName);
            analyzeDiff(repository, oldObjectId, newObjectId);

            // 插入记录表
            Long recordId = insertParsedRecord(projectName, repoDir.getAbsolutePath(), oldBranchName, newBranchName, "成功", null);
            // 插入Diff表
            batchInsertDiffPoJo(oldBranchName, newBranchName, diffPoJoList, recordId);

            return diffPoJoList;
        } catch (IOException e) {
            e.printStackTrace();
            insertParsedRecord(projectName, repoDir.getAbsolutePath(), oldBranchName, newBranchName, "失败", e.getMessage());
        }
        return null;
    }

    @Override
    public List<DiffPoJo> parseCommitDiff(String projectName, File repoDir, String oldCommitHash, String newCommitHash) {
        diffPoJoList = new ArrayList<>();
        try (Repository repository = new RepositoryBuilder().setGitDir(new File(repoDir, ".git")).build()) {
            ObjectId oldObjectId = repository.resolve(oldCommitHash);
            ObjectId newObjectId = repository.resolve(newCommitHash);

            if (oldObjectId == null || newObjectId == null) {
                throw new IOException(String.format("请检查输入的oldCommitHash: %s, newCommitHash: %s", oldCommitHash, newCommitHash));
            }
            analyzeDiff(repository, oldObjectId, newObjectId);

            // 插入记录表
            Long recordId = insertParsedRecord(projectName, repoDir.getAbsolutePath(), oldCommitHash, newCommitHash, "成功", null);

            // 插入Diff表
            batchInsertDiffPoJo(oldCommitHash, newCommitHash, diffPoJoList, recordId);

            return diffPoJoList;
        } catch (IOException e) {
            e.printStackTrace();
            insertParsedRecord(projectName, repoDir.getAbsolutePath(), oldCommitHash, newCommitHash, "失败", e.getMessage());
        }
        return null;
    }

    @Override
    public PageResult<DiffMethodsEntity> selectAllDiffMethods(DiffMethodsListPageRequestPoJo diffMethodsListPageRequestPoJo) {
        PageHelper.startPage(diffMethodsListPageRequestPoJo.getPageNum(), diffMethodsListPageRequestPoJo.getPageSize());
        PageHelper.orderBy(diffMethodsListPageRequestPoJo.getSortBy() + " " + diffMethodsListPageRequestPoJo.getSortOrder());

        List<DiffMethodsEntity> diffMethods = diffMethodsMapper.select((Long)diffMethodsListPageRequestPoJo.getRecordId(), diffMethodsListPageRequestPoJo.getMethodName());

        PageInfo<DiffMethodsEntity> pageInfo = new PageInfo<>(diffMethods);
        return new PageResult<>(pageInfo.getList(), pageInfo.getTotal(), pageInfo.getPageSize(), pageInfo.getPageNum());
    }

    @Override
    public DiffMethodsEntity selectDiffMethodById(Long id) {
        return diffMethodsMapper.selectById(id);
    }

    @Override
    public PageResult<ParsedRecordEntity> selectAllParsedRecords(ParseCodeDiffPageRequestPoJo parseCodeDiffPageRequestPoJo) {
        PageHelper.startPage(parseCodeDiffPageRequestPoJo.getPageNum(), parseCodeDiffPageRequestPoJo.getPageSize());
        PageHelper.orderBy(parseCodeDiffPageRequestPoJo.getSortBy() + " " + parseCodeDiffPageRequestPoJo.getSortOrder());

        List<ParsedRecordEntity> records = parsedRecordsMapper.select(parseCodeDiffPageRequestPoJo.getProjectName(), parseCodeDiffPageRequestPoJo.getStatus());

        PageInfo<ParsedRecordEntity> pageInfo = new PageInfo<>(records);
        return new PageResult<>(pageInfo.getList(), pageInfo.getTotal(), pageInfo.getPageSize(), pageInfo.getPageNum());
    }

    @Override
    @Transactional
    public void deleteRecordById(Long id) {
        deleteDiffMethodsByRecordId(id);
        parsedRecordsMapper.deleteById(id);
    }

    public void deleteDiffMethodsByRecordId(Long recordId) {
        diffMethodsMapper.deleteByRecordId(recordId);
    }

    public static void analyzeDiff(Repository repository, ObjectId oldObjectId, ObjectId newObjectId) {
        try (Git git = new Git(repository)) {
            RevWalk revWalk = new RevWalk(repository);
            RevCommit oldCommit = revWalk.parseCommit(oldObjectId);
            RevCommit newCommit = revWalk.parseCommit(newObjectId);

            CanonicalTreeParser oldTreeParser = new CanonicalTreeParser();
            oldTreeParser.reset(repository.newObjectReader(), oldCommit.getTree());

            CanonicalTreeParser newTreeParser = new CanonicalTreeParser();
            newTreeParser.reset(repository.newObjectReader(), newCommit.getTree());

            List<DiffEntry> diffs = git.diff()
                    .setOldTree(oldTreeParser)
                    .setNewTree(newTreeParser)
                    .call();

            for (DiffEntry diff : diffs) {
                try (ByteArrayOutputStream out = new ByteArrayOutputStream();
                     DiffFormatter formatter = new DiffFormatter(out)) {
                    formatter.setRepository(repository);
                    FileHeader fileHeader = formatter.toFileHeader(diff);
                    formatter.format(diff);

                    // Analyze method-level and line-level changes
                    analyzeMethodChanges(repository, diff, fileHeader.toEditList(), oldCommit, newCommit);
                    out.reset();
                }
            }
        } catch (GitAPIException | IOException e) {
            e.printStackTrace();
        }
    }

    private static void analyzeMethodChanges(Repository repository, DiffEntry diff, EditList editList, RevCommit oldCommit, RevCommit newCommit) throws IOException {
        // 获取新旧分支路径
        // 当新分支中的文件删除时，newPath=/dev/null
        // 当新分支中新增了文件时，oldPath=/dev/null
        String newPath = diff.getNewPath();
        String oldPath = diff.getOldPath();

        // 分析Java文件
        if (newPath.endsWith(".java") || oldPath.endsWith(".java")) {
            String newFileContent = getFileContent(repository, newCommit, newPath);
            String oldFileContent = getFileContent(repository, oldCommit, oldPath);

            // 设置符号解析器
            CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver();
            combinedTypeSolver.add(new ReflectionTypeSolver());

            // 如果你有额外的 jar 文件，可以添加 JarTypeSolver
            // Path jarPath = Paths.get("path/to/your/library.jar");
            // combinedTypeSolver.add(new JarTypeSolver(jarPath));

            SymbolResolver symbolResolver = new JavaSymbolSolver(combinedTypeSolver);
            ParserConfiguration config = new ParserConfiguration();
            config.setSymbolResolver(symbolResolver);

            JavaParser javaParser = new JavaParser(config);

            CompilationUnit newCompilationUnit = newFileContent != null ? javaParser.parse(newFileContent).getResult().orElse(null) : null;
            CompilationUnit oldCompilationUnit = oldFileContent != null ? javaParser.parse(oldFileContent).getResult().orElse(null) : null;

            List<MethodDeclaration> newMethods = newCompilationUnit != null ? newCompilationUnit.findAll(MethodDeclaration.class) : new ArrayList<>();
            List<MethodDeclaration> oldMethods = oldCompilationUnit != null ? oldCompilationUnit.findAll(MethodDeclaration.class) : new ArrayList<>();

            String oldClassName = getClassName(oldCompilationUnit);
            String newClassName = getClassName(newCompilationUnit);

            // 文件为新增文件
            if (diff.getChangeType() == DiffEntry.ChangeType.ADD) {
                if (!newMethods.isEmpty()){
                    newMethods.forEach(method -> {
                        DiffPoJo diffPoJo = new DiffPoJo(newClassName);
                        diffPoJo.setDiffType(EasyCode.DiffType.ADD);
                        int beginLine = method.getBegin().map(r -> r.line).orElse(-1);
                        int endLine = method.getEnd().map(r -> r.line).orElse(-1);
                        diffPoJo.setMethodName(method.getNameAsString());
                        diffPoJo.setMethodDesc(toDescriptor(newCompilationUnit, method));
                        diffPoJo.setDiffDetails(
                                EasyListUtils.add(
                                        new ArrayList<>(),
                                        new DiffMethodDetailPoJo(-1, -1, null, beginLine, endLine, method.toString())
                                )
                        );
                        diffPoJo.setNewContent(method.toString());
                        diffPoJoList.add(diffPoJo);
                    });
                }
            }

            // 文件为删除文件
            if (diff.getChangeType() == DiffEntry.ChangeType.DELETE) {
                if (!oldMethods.isEmpty()){
                    oldMethods.forEach(method -> {
                        DiffPoJo diffPoJo = new DiffPoJo(newClassName);
                        diffPoJo.setDiffType(EasyCode.DiffType.DELETE);
                        int beginLine = method.getBegin().map(r -> r.line).orElse(-1);
                        int endLine = method.getEnd().map(r -> r.line).orElse(-1);
                        diffPoJo.setMethodName(method.getNameAsString());
                        diffPoJo.setMethodDesc(toDescriptor(newCompilationUnit, method));
                        diffPoJo.setDiffDetails(
                                EasyListUtils.add(
                                        new ArrayList<>(),
                                        new DiffMethodDetailPoJo(beginLine, endLine, method.toString(), -1, -1, null)
                                )
                        );
                        diffPoJo.setOldContent(method.toString());
                        diffPoJoList.add(diffPoJo);
                    });
                }
            }

            // 文件为修改状态时
            if (diff.getChangeType() == DiffEntry.ChangeType.MODIFY) {
                // 新增的方法
                if (!newMethods.isEmpty()){
                    for (MethodDeclaration newMethod : newMethods) {
                        boolean methodFound = false;
                        if (!oldMethods.isEmpty()){
                            for (MethodDeclaration oldMethod : oldMethods) {
                                if (newMethod.getSignature().equals(oldMethod.getSignature())) {
                                    methodFound = true;
                                    toDescriptor(newCompilationUnit, newMethod);
                                    // 方法内容不相等，则方法修改
                                    if (!oldMethod.toString().equals(newMethod.toString())){
                                        DiffPoJo diffPoJo = new DiffPoJo(newClassName);
                                        diffPoJo.setMethodName(newMethod.getNameAsString());
                                        diffPoJo.setMethodDesc(toDescriptor(newCompilationUnit, newMethod));
                                        diffPoJo.setDiffType(EasyCode.DiffType.MODIFY);
                                        diffPoJo.setOldContent(oldMethod.toString());
                                        diffPoJo.setNewContent(newMethod.toString());
                                        analyzeLineLevelChanges(oldMethod, oldFileContent, newMethod, newFileContent, editList, diffPoJo);
                                    }
                                    break;
                                }
                            }
                            // 文件中新增的方法
                            if (!methodFound) {
                                int beginLine = newMethod.getBegin().map(r -> r.line).orElse(-1);
                                int endLine = newMethod.getEnd().map(r -> r.line).orElse(-1);

                                diffPoJoList.add(
                                        new DiffPoJo(
                                                newClassName,
                                                newMethod.getNameAsString(),
                                                toDescriptor(newCompilationUnit, newMethod),
                                                EasyCode.DiffType.ADD,
                                                EasyListUtils.add(
                                                        new ArrayList<>(),
                                                        new DiffMethodDetailPoJo(-1, -1, null, beginLine, endLine, newMethod.toString())
                                                ),
                                                null,
                                                newMethod.toString()
                                        )
                                );
                            }
                        }
                    }
                }

                // 删除的方法
                if (!oldMethods.isEmpty()){
                    for (MethodDeclaration oldMethod : oldMethods) {
                        boolean methodFound = false;
                        if (!newMethods.isEmpty()){
                            for (MethodDeclaration newMethod : newMethods) {
                                if (oldMethod.getSignature().equals(newMethod.getSignature())) {
                                    methodFound = true;
                                    break;
                                }
                            }
                            if (!methodFound) {
                                int beginLine = oldMethod.getBegin().map(r -> r.line).orElse(-1);
                                int endLine = oldMethod.getEnd().map(r -> r.line).orElse(-1);
                                diffPoJoList.add(
                                        new DiffPoJo(
                                                oldClassName,
                                                oldMethod.getNameAsString(),
                                                toDescriptor(oldCompilationUnit, oldMethod),
                                                EasyCode.DiffType.DELETE,
                                                EasyListUtils.add(
                                                        new ArrayList<>(),
                                                        new DiffMethodDetailPoJo(beginLine, endLine, oldMethod.toString(), -1, -1, null)
                                                ),
                                                oldMethod.toString(),
                                                null
                                        )
                                );
                            }
                        }
                    }
                }
            }

            // 当为其他状态时如COPY/RENAME时
            if (diff.getChangeType() == DiffEntry.ChangeType.COPY || diff.getChangeType() == DiffEntry.ChangeType.RENAME) {
                System.out.printf("Diff Change Type: %s\n", diff.getChangeType().toString());
            }
        }
    }

    private static String getFileContent(Repository repository, RevCommit commit, String path) throws IOException {
        try (TreeWalk treeWalk = TreeWalk.forPath(repository, path, commit.getTree())) {
            if (treeWalk != null) {
                ObjectId objectId = treeWalk.getObjectId(0);
                ObjectLoader loader = repository.open(objectId);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                loader.copyTo(out);
                return out.toString(StandardCharsets.UTF_8);
            }
        }
        return null;
    }


    private static void analyzeLineLevelChanges(MethodDeclaration oldMethod, String oldFileContent, MethodDeclaration newMethod, String newFileContent, EditList editList, DiffPoJo diffPoJo) {
        if (oldMethod == null || newMethod == null) {
            return;
        }

        int oldMethodBegin = oldMethod.getRange().map(r -> r.begin.line).orElse(-1);
        int oldMethodEnd = oldMethod.getRange().map(r -> r.end.line).orElse(-1);
        int newMethodBegin = newMethod.getRange().map(r -> r.begin.line).orElse(-1);
        int newMethodEnd = newMethod.getRange().map(r -> r.end.line).orElse(-1);

        List<String> newMethodLines = newFileContent.lines().collect(Collectors.toList());
        List<String> oldMethodLines = oldFileContent.lines().collect(Collectors.toList());

        List<DiffMethodDetailPoJo> diffMethodDetailPoJoList = new ArrayList<>();
        for (Edit edit : editList) {
            // 获取Type
            if (edit.getType() == Edit.Type.INSERT) {
                // 位于新方法中
                if (edit.getBeginB() >= newMethodBegin && edit.getEndB() - 1 <= newMethodEnd) {
                    diffMethodDetailPoJoList.add(
                            new DiffMethodDetailPoJo(
                                    edit.getBeginA(),
                                    edit.getEndA(),
                                    null,
                                    edit.getBeginB(),
                                    edit.getEndB() - 1,
                                    getChangeBody(newMethodLines, edit.getBeginB(), edit.getEndB() - 1)
                            )
                    );
                }
            } else if (edit.getType() == Edit.Type.DELETE) {
                if (edit.getBeginA() >= oldMethodBegin && edit.getEndA() - 1 <= oldMethodEnd) {
                    diffMethodDetailPoJoList.add(
                            new DiffMethodDetailPoJo(
                                    edit.getBeginA(),
                                    edit.getEndA() - 1,
                                    getChangeBody(oldMethodLines, edit.getBeginA(), edit.getEndA() - 1),
                                    edit.getBeginB(),
                                    edit.getEndB(),
                                    null)
                    );
                }

            } else if (edit.getType() == Edit.Type.REPLACE) {
                if (edit.getBeginA() >= oldMethodBegin && edit.getEndA() - 1 <= oldMethodEnd && edit.getBeginB() >= newMethodBegin && edit.getEndB() - 1 <= newMethodEnd){
                    diffMethodDetailPoJoList.add(
                            new DiffMethodDetailPoJo(
                                    edit.getBeginA(),
                                    edit.getEndA() - 1,
                                    getChangeBody(oldMethodLines, edit.getBeginA(), edit.getEndA() - 1),
                                    edit.getBeginB(),
                                    edit.getEndB() - 1,
                                    getChangeBody(newMethodLines, edit.getBeginB(), edit.getEndB() - 1)
                            )
                    );
                }
            } else {

            }
        }

        if (!diffMethodDetailPoJoList.isEmpty()){
            diffPoJo.setDiffDetails(diffMethodDetailPoJoList);
            diffPoJoList.add(diffPoJo);
        }

    }

    private static String getChangeBody(List<String> lines, int starts, int ends) {
        if (ends < starts) {
            return null;
        }
        List<String> res = new ArrayList<>();
        for (int i = starts; i <= ends; i++){
            res.add(lines.get(i));
        }
        return EasyListUtils.asString(res, "\\n");
    }

    private static String getClassName(CompilationUnit cu){
        if (cu != null) {
            String packageName = cu.getPackageDeclaration().map(NodeWithName::getNameAsString).orElse(null);
            for (TypeDeclaration<?> td : cu.getTypes()){
                if (td instanceof ClassOrInterfaceDeclaration){
                    return packageName + "." + td.getNameAsString();
                }
            }
        }
        return null;
    }

    public static String toDescriptor(CompilationUnit cu, MethodDeclaration method) {
        StringBuilder descriptor = new StringBuilder();
        descriptor.append("(");

        for(int i = 0; i < method.getParameters().size(); ++i) {
            String parameterTypeDesc;
            try {
                parameterTypeDesc = method.getParameter(i).getType().toDescriptor();
            } catch (UnsolvedSymbolException e){
                parameterTypeDesc = getTypeDescriptor(cu, method.getParameter(i).getType());
            }
            descriptor.append(parameterTypeDesc);
        }

        descriptor.append(")");

        // 添加方法的type
        String methodTypeDesc;
        try {
            methodTypeDesc = method.getType().toDescriptor();
        } catch (UnsolvedSymbolException e){
            methodTypeDesc = getTypeDescriptor(cu, method.getType());
        }
        descriptor.append(methodTypeDesc);
        return descriptor.toString();
    }

    private static String getTypeDescriptor(CompilationUnit cu, Type type) {
        switch (type.asString()) {
            case "byte": return "B";
            case "char": return "C";
            case "double": return "D";
            case "float": return "F";
            case "int": return "I";
            case "long": return "J";
            case "short": return "S";
            case "boolean": return "Z";
            case "void": return "V";
            default:
                if (type.isArrayType()) {
                    return "[" + getTypeDescriptor(cu, type.asArrayType().getComponentType());
                } else if (type instanceof ClassOrInterfaceType) {
                    ClassOrInterfaceType classType = (ClassOrInterfaceType) type;
                    return "L" + resolveFullQualifiedName(cu, classType).replace('.', '/') + ";";
                } else {
                    return "L" + type.asString().replace('.', '/') + ";";
                }
        }
    }

    private static String resolveFullQualifiedName(CompilationUnit cu, ClassOrInterfaceType type) {
        String className = type.getNameAsString();

        // 查找是否有import语句
        Optional<String> fullQualifiedName = cu.getImports().stream()
                .map(NodeWithName::getNameAsString)
                .filter(nameAsString -> nameAsString.endsWith("." + className))
                .findFirst();

        if (fullQualifiedName.isPresent()) {
            return fullQualifiedName.get();
        } else {
            // 使用包名和类名组合
            Optional<String> optPackageName = cu.getPackageDeclaration().map(NodeWithName::getNameAsString);
            String packageName = optPackageName.orElse("");
            return packageName.isEmpty() ? className : packageName + "." + className;
        }
    }

    private void batchInsertDiffPoJo(String oldCommit, String newCommit, List<DiffPoJo> diffPoJoList, Long recordId){
        if (diffPoJoList.isEmpty()){
            return;
        }
        List<DiffMethodsEntity> diffMethods = new ArrayList<>();
        diffPoJoList.forEach(diffPoJo -> EasyListUtils.add(diffMethods, new DiffMethodsEntity(diffPoJo.getName(), diffPoJo.getMethodName(), diffPoJo.getMethodDesc(), diffPoJo.getDiffType(), oldCommit, diffPoJo.getOldContent(), newCommit, diffPoJo.getNewContent(), recordId)));
        diffMethodsMapper.batchInsert(diffMethods);
    }

    private Long insertParsedRecord(String projectName, String repoDir, String oldBranch, String newBranch, String status, String remark){
        ParsedRecordEntity parsedRecordEntity = new ParsedRecordEntity(projectName, repoDir, oldBranch, newBranch, status, remark);
        parsedRecordsMapper.insert(parsedRecordEntity);
        return parsedRecordEntity.getId();
    }

    private void init_db(){
        diffMethodsMapper.deleteAll();
    }
}